#include <assert.h>

#include <iostream>
#include <map>

#include <boost/lexical_cast.hpp>

#include "filesystem.hpp"
#include "foreach.hpp"
#include "model.hpp"
#include "string_utils.hpp"
#include "wml_node.hpp"
#include "wml_parser.hpp"

namespace graphics
{

namespace {
std::map<std::string, const_model_ptr> cache;
}

const_model_ptr model::get(const std::string& id)
{
	const_model_ptr& entry = cache[id];
	if(entry) {
		return entry;
	}

	model* m = new model(wml::parse_wml(sys::read_file("models/" + id + ".cfg")));
	m->id_ = id;

	entry.reset(m);
	return entry;
}

model::model(wml::const_node_ptr node)
  : t_(graphics::texture::get(node->attr("texture")))
{
	std::vector<std::string> scale = util::split(node->attr("scale"));
	if(scale.size() != 3) {
		for(int n = 0; n != 3; ++n) {
			scale_[n] = 1.0;
		}
	} else {
		for(int n = 0; n != 3; ++n) {
			scale_[n] = boost::lexical_cast<GLfloat>(scale[n]);
		}
	}

	wml::node::const_child_iterator i1 = node->begin_child("vertex");
	wml::node::const_child_iterator i2 = node->end_child("vertex");
	for(; i1 != i2; ++i1) {
		vertex v;
		v.id = i1->second->attr("id");
		
		std::vector<std::string> pos = util::split(i1->second->attr("pos"));
		if(pos.size() != 3) {
			std::cerr << "BAD POS: " << i1->second->attr("pos") << "\n";
			continue;
		}

		for(int n = 0; n != 3; ++n) {
			v.pos[n] = boost::lexical_cast<GLfloat>(pos[n]);
		}

		std::vector<std::string> uv = util::split(i1->second->attr("uv"));
		if(uv.size() != 2) {
			std::cerr << "BAD UV: " << i1->second->attr("uv") << "\n";
			continue;
		}

		for(int n = 0; n != 2; ++n) {
			v.uv[n] = boost::lexical_cast<GLfloat>(uv[n]);
		}

		vertices_.push_back(v);
	}

	i1 = node->begin_child("face");
	i2 = node->end_child("face");
	for(; i1 != i2; ++i1) {
		polygon face;
		std::vector<std::string> points = util::split(i1->second->attr("points"));
		foreach(const std::string& point, points) {
			int v = -1;
			for(int n = 0; n != vertices_.size(); ++n) {
				if(vertices_[n].id == point) {
					v = n;
					break;
				}
			}

			if(v == -1) {
				std::cerr << "ERROR: Unrecognized point in model: '" << point << "'\n";
				continue;
			}

			face.points.push_back(v);
		}

		if(face.points.size() >= 3) {
			polygons_.push_back(face);
		}
	}
}

void model::draw() const
{
	glDisable(GL_LIGHTING);
	glPushMatrix();
	glScalef(scale_[0], scale_[1], scale_[2]);
	t_.set_as_current_texture();
	foreach(const polygon& p, polygons_) {
		glBegin(GL_POLYGON);
		foreach(int v, p.points) {
			draw_vertex(v);
		}
		glEnd();
	}
	glPopMatrix();
	glEnable(GL_LIGHTING);
}

void model::draw_vertex(int n) const
{
	assert(n >= 0 && n < vertices_.size());
	t_.set_coord(vertices_[n].uv[0], vertices_[n].uv[1]);
	glVertex3fv(&vertices_[n].pos[0]);
}

}
